/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       army_dialog.h

	$Header: /game/army_dialog.h $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "army_dialog.h"

#include "abbreviate_number.h"
#include "abstract_town.h"
#include "adv_artifact_pile.h"
#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_map_window.h"
#include "adaptor_handler.h"
#include "army.h"
#include "army_list.h"
#include "army_list_window.h"
#include "artifact_icon.h"
#include "artifact_keyword.h"
#include "artifact_properties.h"
#include "artifact_type.h"
#include "bitmap_group_cache.h"
#include "bitmap_layer_cache_window.h"
#include "bound_handler.h"
#include "button_cache.h"
#include "cached_grail_data_source.h"
#include "cheats.h"
#include "combat_model_window.h"
#include "creature.h"
#include "creature_array_window.h"
#include "creature_traits.h"
#include "drag_artifact.h"
#include "drag_creature.h"
#include "enum_operations.h"
#include "external_string.h"
#include "format_string.h"
#include "function_handler.h"
#include "get_artifact_damage_modifier.h"
#include "hero.h"
#include "hero_class_properties.h"
#include "hero_keyword_replacer.h"
#include "interface_help.h"
#include "luck_icon_window.h"
#include "map_point.h"
#include "morale_icon_window.h"
#include "platform.h"
#include "player.h"
#include "replace_keywords.h"
#include "simple_dialog.h"
#include "skill_icon.h"
#include "skill_properties.h"
#include "spellbook_window.h"
#include "standard_fonts.h"
#include "text_window.h"
#include "toggle_button.h"
#include "town_type.h"

extern t_bitmap_group_cache g_army_layout;

extern t_button_cache			g_dismiss_button;
extern t_button_cache			g_divide_button;
extern t_button_cache			g_divide_single_button;
extern t_button_cache			g_ok_button;
extern t_external_string const	k_text_hero_level;

extern t_bitmap_group_cache g_interface_800;
extern t_bitmap_group_cache g_interface_1024;
extern t_bitmap_group_cache g_interface_1280;

// -------------------------------------------------------------------
// Unnamed namespace
// -------------------------------------------------------------------
namespace
{
    int k_max_army_windows = 5;
    
    t_external_string const k_army_right_click_text( "army_right_click_text.misc" );
}

// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// -----------------------------------------------------------------------
t_army_dialog::t_army_dialog( t_army* army, t_army* adjacent_army, 
							  t_adventure_frame* adventure_frame, int selected_slot )
			 //: t_bitmap_layer_window( 0, t_screen_point(0,0), adventure_frame )
			 : t_army_dialog_base( &army->get_creatures(), adjacent_army, adventure_frame, selected_slot )
{
	m_army = army;
	m_adjacent_army = adjacent_army;

	create_buttons();
	setup_temp_army();
	create_creature_windows();
	check_for_allied_and_graveyard();
	create_icons();
	create_text_boxes();

	// create army list
	t_help_block const& help = get_help_block( "army_screen" );

	m_army_up_button = create_button( "army_up" );
	m_army_up_button->set_click_handler( bound_handler( *this, &t_army_dialog::army_up_clicked ) );
  set_help( m_army_up_button, help, "click_army_up" );

	m_army_down_button = create_button( "army_down" );
	m_army_down_button->set_click_handler( bound_handler( *this, &t_army_dialog::army_down_clicked ) );
  set_help( m_army_down_button, help, "click_army_down" );

	t_screen_rect rect = m_layout->find( "army_list")->get_rect();
	m_army_list = new t_army_list_window( rect, this );
	m_army_list->set_select_handler( bound_handler( *this, &t_army_dialog::select_army ) );
	m_army_list->set_limit_handler( bound_handler( *this, &t_army_dialog::update_scroll_buttons ) );
	m_army_list->set_army_array( m_army->get_map()->get_player().get_armies() );
	m_army_list->select_army( m_army );
	

	// select first creature
	if (selected_slot < 0)
		select_leader();
	else
		m_army_window->select_creature( selected_slot );
}

void t_army_dialog::army_down_clicked( t_button* button )
{
	m_army_list->move_list( 1 );
}

void t_army_dialog::army_up_clicked( t_button* button )
{
	m_army_list->move_list( -1 );
}

// -----------------------------------------------------------------------
// setup a possible newly-created adjacent army
// -----------------------------------------------------------------------
void t_army_dialog::setup_temp_army()
{
	assert( m_adventure_frame );

	if (m_adjacent_army != 0)
		return;

	// Don't need a target for splitting if we already have too many armies or are a graveyard.
	if ( m_army->get_owner() == NULL || !m_army->get_owner()->can_create_army() || m_army->is_graveyard() )
		return;

	t_adv_map_point point;

	m_temp_army = new t_army;
	m_temp_army->get_creatures().clear();
	m_temp_army->set_owner( m_army->get_owner_number() );

	if (!m_army->find_adjacent_space( point, *m_army, false ))
		return;

	m_adjacent_army = m_temp_army;
	m_temp_army_position = point;
}


// -----------------------------------------------------------------------
// Determine transfer costs of armies
// -----------------------------------------------------------------------
void t_army_dialog::get_transfer_costs( int & cost_in, int & cost_out ) const
{
	if (m_adjacent_army == NULL || m_army->is_graveyard() || m_adjacent_army->is_graveyard())
	{
		// It's free!
		cost_in = 0;
		cost_out = 0;
	} 
	else
	{
		t_adventure_map const&  map = *m_army->get_map();
		t_level_map_point_2d    position = m_army->get_position();
		t_adventure_tile const& tile = map[ position ];
		t_terrain_type          terrain = tile.get_terrain();
		t_level_map_point_2d    adjacent_point;
		
		if ( m_adjacent_army == m_temp_army.get() )
		{
			adjacent_point = m_temp_army_position;
		}
		else
		{
			adjacent_point = m_adjacent_army->get_position();
		}

		t_adventure_tile const& new_tile = map[ adjacent_point ];
		t_terrain_type          new_terrain = new_tile.get_terrain();

		cost_in = get_terrain_cost( terrain );
		cost_out = get_terrain_cost( new_terrain );

		if (position.row != adjacent_point.row && position.column != adjacent_point.column)
		{
			// Increase cost by 50% for diagonal moves
			cost_in += cost_in >> 1;
			cost_out += cost_out >> 1;
		}
	}
}



// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// close creature windows
// -----------------------------------------------------------------------
void t_army_dialog::close_creature_windows()
{
	t_window_ptr ref = this;

	// Cancel any in progress drag operation
	cancel_drag();

	// create a new, adjacent army if appropriate.
	t_army* adjacent_army = m_adventure_frame->create_army( m_army, m_adjacent_army, m_temp_army );
	if (adjacent_army == 0 && m_adjacent_army != 0 )
	{
		t_army_map::iterator index = m_trading_armies.find( m_adjacent_army );

		if (index != m_trading_armies.end())
			m_trading_armies.erase( index );
	}

	// check if the primary army needs to be deleted.
	if (!m_adventure_frame->update_army( m_army ))
	{
		t_army_map::iterator index = m_trading_armies.find( m_army );

		if (index != m_trading_armies.end())
			m_trading_armies.erase( index );
		m_army = 0;
	}

	// if both the current army and the adjacent army exist, associate them.
	if (m_army != 0)
		m_trading_armies[ m_army ] = adjacent_army;

	if (adjacent_army != 0)
		m_trading_armies[ adjacent_army ] = m_army;

	t_army_dialog_base::close_creature_windows();

	// check if newly created army should be attacked
	m_adjacent_army = adjacent_army;
	if (adjacent_army != 0)
	{
		t_adventure_map_window* map_window = m_adventure_frame->get_map_window();

		map_window->check_enemy_attack( adjacent_army, bound_handler( *this, &t_army_dialog::enemy_attack_handler ) );
	}

	// Select an army
	t_adventure_map* map = m_adventure_frame->get_map();

	if (m_adjacent_army != 0 
		&& m_adjacent_army->get_owner_number() == map->get_player_number())
		m_adventure_frame->select_army( m_adjacent_army );
	else if (m_army != 0 && m_army->get_owner_number() == map->get_player_number())
		m_adventure_frame->select_army( m_army );

	m_temp_army = NULL;
}

// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// select a new army
// -----------------------------------------------------------------------
void t_army_dialog::select_army( t_army* army )
{
	// Hold a reference on the army we're trying to select in case close_creature_windows destroys it
	t_army_ptr new_army = army;
	t_window_ptr ref = this;

	close_creature_windows();
	if (!is_open())
		return;

	// See if the army we're trying to select is still valid
	if (new_army->empty())
	{
		new_army = m_army;
		if (new_army == 0)
			new_army = m_adjacent_army;
	}
				
	// Select the army we decided upon
	m_army = new_army;
	m_creatures = m_army;
	
	t_army_map::iterator index = m_trading_armies.find( m_army );
	
	if (index == m_trading_armies.end())
		m_adjacent_army = 0;
	else
		m_adjacent_army = index->second;

	setup_temp_army();
	create_creature_windows();

	m_army_list->update();
	m_army_list->select_army( m_army );

	t_adventure_map* map = m_adventure_frame->get_map();

	if (m_army != 0 && m_army->get_owner_number() == map->get_player_number())
		m_adventure_frame->select_army( m_army );

	check_for_allied_and_graveyard();
	m_army_window->select_leader();
}


// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// Validate a drag-drop in or out of a graveyard
// -----------------------------------------------------------------------
void t_army_dialog::validate_graveyard_drag_drop( t_creature_array_window::t_drag_drop_validate_data const & data, bool & accept )
{
	// Can drag anything out. Cannot drag anything except dead heroes in
	t_drag_creature const * drag_creature = dynamic_cast< t_drag_creature const * >( data.drag_source );

	if ( drag_creature )
	{
		t_creature_array_window * graveyard_window;
		t_creature_array_window * army_window;
		if ( m_army->is_graveyard() )
		{
			graveyard_window = m_army_window;
			army_window = m_adjacent_army_window;
		}
		else
		{
			graveyard_window = m_adjacent_army_window;
			army_window = m_army_window;
		}


		if ( drag_creature->get_source_window() == data.drag_target )
		{
			// Can always rearrange within an army
			accept = true;
		}
		else if ( drag_creature->get_source_window() == army_window )
		{
			if ( drag_creature->get_data()->is_dead() )
			{
				accept = true;
			}
			else 
			{
				accept = false;
			}
		}
		else
		{
			assert( drag_creature->get_source_window() == graveyard_window );

			// Don't allow swap-outs
			t_creature_stack const & target_stack = (*data.drag_target->get_army())[data.drag_target_slot];
			
			if ( target_stack.get_number() > 0 && !target_stack.is_dead() )
			{
				accept = false;
			}
			else
			{
				accept = true;
			}
		}

		return;
	}

	// Allow artifacts in or out freely
	accept = true;
	return;
}

// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// Validate a drag-drop in or out of an ally's graveyard
// -----------------------------------------------------------------------
void t_army_dialog::validate_allied_graveyard_drag_drop( t_creature_array_window::t_drag_drop_validate_data const & data, bool & accept, t_creature_array_window::t_drag_validate_handler allied_handler )
{
	// Restrictions are union of both allied and graveyard restrictions
	accept = true;

	validate_graveyard_drag_drop( data, accept );

	if ( !accept )
		return;

	allied_handler( data, accept );
}

// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// Check for the allied and/or graveyard restrictions on drag drops
// -----------------------------------------------------------------------
void t_army_dialog::check_for_allied_and_graveyard()
{
	bool graveyard_restrictions = ( ( m_army && m_army->is_graveyard() ) || ( m_adjacent_army && m_adjacent_army->is_graveyard() ) );

	if ( check_for_allied() )
	{
		if ( graveyard_restrictions )
		{
			if ( m_adjacent_army_window )
			{
				t_creature_array_window::t_drag_validate_handler allied_handler = m_adjacent_army_window->get_drag_validate_handler();
				m_adjacent_army_window->set_drag_validate_handler( add_3rd_argument( bound_handler( *this, &t_army_dialog::validate_allied_graveyard_drag_drop ), allied_handler ) );
			}

			if ( m_army_window )
			{
				t_creature_array_window::t_drag_validate_handler allied_handler = m_army_window->get_drag_validate_handler();
				m_army_window->set_drag_validate_handler( add_3rd_argument( bound_handler( *this, &t_army_dialog::validate_allied_graveyard_drag_drop ), allied_handler ) );
			}
		}
	}
	else if ( graveyard_restrictions )
	{
		if ( m_adjacent_army_window )
			m_adjacent_army_window->set_drag_validate_handler( bound_handler( *this, &t_army_dialog::validate_graveyard_drag_drop ) );

		if ( m_army_window )
			m_army_window->set_drag_validate_handler( bound_handler( *this, &t_army_dialog::validate_graveyard_drag_drop ) );
	}
}

// -----------------------------------------------------------------------
// dialog to manipulate one or two armies
// If the last creature in the army is dismissed, find a place to put the 
// resulting artifact pile
// -----------------------------------------------------------------------
void t_army_dialog::get_artifact_pile_position( t_adv_map_point & point, t_counted_ptr< t_adv_artifact_pile > & pile )
{
	// Always goes on army's current position
	if ( m_selected_creature_array == m_temp_army ) 
		point = m_temp_army_position;
	else
		point = m_selected_creature_array->get_position();

	// Look for existing pile
	t_adv_artifact_pile::is_good_creation_point( point, pile, m_army->get_map() );
}


// -----------------------------------------------------------------------
// Update the visibility of our scroll up/down buttons on our army list
// based on callbacks from the army_list_window
// -----------------------------------------------------------------------
void t_army_dialog::update_scroll_buttons( int limit_flags )
{
	assert( m_army_up_button && m_army_down_button );
	m_army_up_button->set_visible( !(limit_flags & k_army_list_window_at_top) );
	m_army_down_button->set_visible( !(limit_flags & k_army_list_window_at_bottom) );
}


// -----------------------------------------------------------------------
// Make sure no artifacts remain in graveyards we are responsible for
// -----------------------------------------------------------------------
void t_army_dialog::pull_artifacts_from_graveyards( )
{
	assert( m_temp_army == NULL );

	t_player * player = &m_adventure_frame->get_map()->get_player();

	if ( m_army && m_army->is_graveyard() )
		t_adv_artifact_pile::pull_artifacts_from_array( *m_army, player );

	if ( m_adjacent_army && m_adjacent_army->is_graveyard() )
		t_adv_artifact_pile::pull_artifacts_from_array( *m_adjacent_army, player );

	t_army_map::iterator it;

	for ( it = m_trading_armies.begin(); it != m_trading_armies.end(); it++ )
	{
		if ( it->first && it->first->is_graveyard() )
		{
			t_adv_artifact_pile::pull_artifacts_from_array( *it->first, player );
		}
		
		if ( it->second && it->second->is_graveyard() )
		{
			t_adv_artifact_pile::pull_artifacts_from_array( *it->second, player );
		}
	}
}

// -----------------------------------------------------------------------
// when the window closes, make sure than any changes we might have
// made are reflected in the adventure frame.
// -----------------------------------------------------------------------
void t_army_dialog::on_close()
{
	t_army_dialog_base::on_close();

	// Graveyards cannot hold artifacts. Go through all armies & find any graveyards; 
	// if any graveyards are found, put any artifacts into a booty pile
	pull_artifacts_from_graveyards( );


	m_adventure_frame->update();
}

// -----------------------------------------------------------------------
// Make sure any new armies trigger their events; don't let players sneak past placed events
// by doing repeated splits
// -----------------------------------------------------------------------
void t_army_dialog::trigger_events()
{
	// Make sure scripting system can't delete adventure map
	t_counted_ptr< t_adventure_map > map = m_adventure_frame->get_map();
	
	// Conveniently, trigger_events() will not retrigger if the army is the same one that was
	// there before
	t_army_map::iterator it;

	for ( it = m_trading_armies.begin(); it != m_trading_armies.end(); it++ )
	{
		if ( it->first )
			it->first->trigger_events();

		if ( map->is_game_over() )
			return;

		if ( it->second )
			it->second->trigger_events();

		if ( map->is_game_over() )
			return;
	}
}

// -----------------------------------------------------------------------
// This allows the caller to set up a handler to be called if anything
// that happens within this window causes an enemy to start moving
// -----------------------------------------------------------------------
void t_army_dialog::set_enemy_attack_handler( t_handler enemy_attack_handler )
{
	m_enemy_attack_handler = enemy_attack_handler;
}


// -----------------------------------------------------------------------
// This is called by check_enemy_attack() if an attack is actually 
// started. If so, close up and get out!
// -----------------------------------------------------------------------
void t_army_dialog::enemy_attack_handler()
{
	// Tell parent window too
	m_enemy_attack_handler();

	close();
}
